古くさい JS で書かれた UI ライブラリ を React に包み込んだ時の話
アルバイトをしていた時に React でフロントエンドをリプレースできるのか?という難題を振られた
基本的にはいけるけどとある UI ライブラリがキツそうだよね~という感じ
頑張って包み込めないか検討した
詳細は NDA です!
そのライブラリは script タグで読み込んで new Hoge(mountTargetId: string) で指定した id を持つ div に生成
あとはインスタンスをこねこねして高度な動きをしてくれる
なつかしい奴
実現したいこと
1つの画面に複数のインスタンスを生成しても破綻しないこと
Functional Component で書きたい
TypeScript で型をつけたい
どうでもいいこと
パフォーマンス
そいつが死ぬほど遅いからそこまでクリティカルじゃなかった
こうやって解決しました
ライブラリを使うコンポーネントで useRef してライブラリをラップしたコンポーネントに ref を渡す
UI ライブラリを new Hoge(someRandomId) でランダム生成した div にマウントして渡された ref にインスタンスを収納
あとは呼び出し元で ref に入ってるインスタンスをこねて操作
コード例
code:hoge.d.ts
interface Hoge {
init(): void
destructor(): void
parse(data: any, type: string): void
}
interface hogeConstructor {
/**
* Make hogeObject and DOM
*
* @param id target div id
*/
new (id: string): Hoge
}
// eslint-disable-next-line no-unused-vars
declare const Hoge: hogeConstructor
code:page.tsx
import * as React from 'react'
import { Hoge } from './components/hoge'
export const Page: React.FC = () => {
const ref = useRef<Hoge | null>(null)
useLayoutEffect(() => {
if (ref.current !== null) {
ref.current.parse({/* some data*/}, 'json')
}
}, [])
return (
<div>
<Hoge
// some settings
skin="dark"
hogeRef={ref}
/>
</div>
)
}
code:hoge.tsx
import * as React from 'react'
const { useLayoutEffect, useState, useRef } = React
type Props = {
skin?: string
hogeRef: React.MutableRefObject<Hoge | null>
}
export const Hoge: React.FC<Props> = ({
skin,
hogeRef
}: Props) => {
`hoge_${Math.random()
.toString(36)
.slice(-8)}`
)
useLayoutEffect(() => {
const hoge = new Hoge(divId)
if (skin) grid.setSkin(skin)
hoge.init()
// eslint-disable-next-line no-param-reassign
hoge.current = hoge
return () => hoge.destructor()
}, [])
return (
<div>
<div id={divId} style={{ height: '100%' }} />
</div>
)
}
その UI ライブラリがメソッド多すぎて外にまるっと出さないとつらかったので…
useImperativeHandle を使うといいらしい
まあ親が毎回 ref 作ってあげないといけないのダルいしね
違うか…
試した限りでは大変なことにならなかったので多分これでいけてるんだと思う
参考にしてね
ちなみにですが検証した結果リプレース周りがどうなったかは知りません、退社したので…